package net.cyndeline.GUI.rendering

import net.cyndeline.gameMap.{TileContents, Grid}
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
import net.cyndeline.scalarlib.entityComponent.miranda.{Entity, EntityComponentManager}
import net.cyndeline.GUI.graphics.Representation
import net.cyndeline.entityComponent.components.gui.{GraphicsComponent, CameraComponent}
import net.cyndeline.entityComponent.components.gameMap.PositionComponent
import net.cyndeline.GUI.setup.SpriteReader

/**
 * Iterates over the current game map every frame and selects which entities that should be drawn to the GUI.
 * Coordinates to draw at are adjusted so that entities with the camera component is positioned in the center
 * of the drawing.
 *
 * @param entityManager Used to access map entities to find out how and if to render them.
 * @param graphics Stores graphical objects for every entity (if they've been assigned any).
 * @param map The map that should be rendered.
 * @param cameraEntity Id of the entity that has the camera component.
 * @param canvasWidth The width in pixels of the area that the map is being drawn upon.
 * @param canvasHeight The height in pixels of the area that the map is being drawn upon.
 */
class JobScheduler(entityManager: EntityComponentManager,
                   graphics: GraphicsContainer,
                   map: Grid,
                   cameraEntity: Entity,
                   canvasWidth: Int,
                   canvasHeight: Int) {
  private val positionComponentKey = entityManager.componentType(classOf[PositionComponent])

  /** Iterates over the map and creates renders for every entity on it. */
  def schedule: RenderJob = {

    /* If there's an entity to focus the camera on, adjust all coordinates to place it in the center. */
    val camera: (Int, Int) = getCameraPos
    val center: (Int, Int) = getCenterPixelCoordinates
    val adjustment = (center._1 - camera._1, center._2 - camera._2)

    val agents       = new ListBuffer[Render]()
    val items        = new ListBuffer[Render]()
    val traps        = new ListBuffer[Render]()
    val decorations  = new ListBuffer[Render]()
    val environments = new ListBuffer[Render]()

    var i = 0
    var j = 0

    while (i < map.width) {
      while (j < map.height) {
        val tile = map.tile(i, j)

        //TODO hide non-visible tiles

        if (tile.agent.isDefined) {
          addRepresentation(tile.agent.get, adjustment, agents)
          processAnimation(graphics.getGraphic(tile.agent.get))
        }

        if (tile.item.isDefined) {
          addRepresentation(tile.item.get, adjustment, items)
          processAnimation(graphics.getGraphic(tile.item.get))
        }

        if (tile.trap.isDefined) {
          addRepresentation(tile.trap.get, adjustment, traps)
          processAnimation(graphics.getGraphic(tile.trap.get))
        }

        if (tile.decoration.isDefined) {
          addRepresentation(tile.decoration.get, adjustment, decorations)
          processAnimation(graphics.getGraphic(tile.decoration.get))
        }

        if (tile.decoration.isDefined) {
          addRepresentation(tile.decoration.get, adjustment, environments)
          processAnimation(graphics.getGraphic(tile.decoration.get))
        }

        j += 1
      }
      i += 1
    }

    val finalRender = new ListBuffer[ListBuffer[Render]]()
    if (!agents.isEmpty) finalRender append agents
    if (!items.isEmpty) finalRender append items
    if (!traps.isEmpty) finalRender append traps
    if (!decorations.isEmpty) finalRender append decorations
    if (!environments.isEmpty) finalRender append environments

    RenderJob(finalRender)
  }

  /** Creates a render of an entity that has a graphical component and adds it to a list. */
  private def addRepresentation(entity: Entity, adjustment: (Int, Int), list: ListBuffer[Render]) {

    /* Fetch the graphical representation of this entity, or assign it a new one. */
    val representation = graphics.getGraphic(entity).getOrElse {
      return
    }

    val render = Render(representation.getView, representation.x - adjustment._1, representation.y - adjustment._2)
    list append render
  }

  private def getCameraPos: (Int, Int) = {
    val position: PositionComponent = entityManager.getComponent(cameraEntity, positionComponentKey)
    (position.x, position.y)
  }

  private def processAnimation(animation: Option[Representation]) {
    if (animation.isDefined)
      animation.get.process()
  }

  private def getCenterPixelCoordinates: (Int, Int) = (Math.floor(canvasWidth / 2.0).toInt, Math.floor(canvasHeight / 2.0).toInt)
}
